package org.gpf;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * 
* @ClassName: JDBCUtil 
* @Description: JDBC工具类
* <ul>
* 	<li>获取数据库连接</li>
* 	<li>释放数据库资源</li>
* 	<li>事务的提交和回滚</li>
* </ul>
* @author gaopengfei
* @date 2015-10-14 下午10:15:26 
*
 */
public class JDBCUtils {

	private JDBCUtils(){
		
	}
	
	private static DataSource dataSource = null;
	/**
	 * 数据源应该只被初始化一次
	 */
	static{
		dataSource = new ComboPooledDataSource("c3p0");
	}
	
	/**
	 * 通过数据库连接池获取数据库连接
	 * c3p0config.xml的named-config属性为c3p0
	 */
	public static Connection getConnection() throws Exception {
		return dataSource.getConnection();
	}
	
	/**
	 * 释放数据库资源
	 * @param resultSet：结果集对象
	 * @param statement：语句对象
	 * @param connection：连接对象
	 */
	public static void release(ResultSet resultSet, Statement statement,
			Connection connection) {

		if (resultSet != null) {
			try {
				resultSet.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}

		if (statement != null) {
			try {
				statement.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}

		if (connection != null) {
			try {
				/**
				 * 数据库连接池的 Connection 对象进行 close 时并不是真的进行关闭, 而是把该数据库连接会归还到数据库连接池中. 
				 */
				connection.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 开始事务
	 * 
	 * 事务的隔离级别 在 JDBC 程序中可以通过 Connection 的 setTransactionIsolation 来设置事务的隔离级别
	 */
	public static void beginTransaction(Connection connection){
		if(connection != null){
			try {
				connection.setAutoCommit(false);
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 提交事务
	 */
	public static void commit(Connection connection){
		if(connection != null){
			try {
				connection.commit();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 回滚事务
	 */
	public static void rollback(Connection connection){
		if(connection != null){
			try {
				connection.rollback();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 通用的查询方法：可以根据传入的 SQL、Class 对象返回 SQL 对应的记录的对象
	 * @param clazz: 描述对象的类型
	 * @param sql: SQL 语句。可能带占位符
	 * @param args: 填充占位符的可变参数。
	 * @return
	 */
	public <T> T get(Class<T> clazz, String sql, Object... args) {
		T entity = null;

		Connection connection = null;
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;

		try {
			//1. 得到 ResultSet 对象
			connection = JDBCUtils.getConnection();
			preparedStatement = connection.prepareStatement(sql);
			for (int i = 0; i < args.length; i++) {
				preparedStatement.setObject(i + 1, args[i]);
			}
			resultSet = preparedStatement.executeQuery();

			//2. 得到 ResultSetMetaData 对象。
			/* ResultSetMetaData: 描述结果集的元数据. 
			 * 可以得到结果集中的基本信息: 结果集中有哪些列, 列名, 列的别名等.
			 * 结合反射可以写出通用的查询方法.
			 * 
			 * 类似的，DatabaseMetaData 是描述 数据库（版本号、数据库中的表，用户名） 的元数据对象.可以通过Connection得到。
			 */
			ResultSetMetaData rsmd = resultSet.getMetaData();
			
			//3. 创建一个 Map<String, Object> 对象, 键: SQL 查询的列的别名, 值: 列的值
			Map<String, Object> values = new HashMap<>();
			
			//4. 处理结果集. 利用 ResultSetMetaData 填充 3 对应的 Map 对象
			if(resultSet.next()){
				for(int i = 0; i < rsmd.getColumnCount(); i++){
					String columnLabel = rsmd.getColumnLabel(i + 1);
					Object columnValue = resultSet.getObject(i + 1);
					
					values.put(columnLabel, columnValue);
				}
			}
			
			//5. 若 Map 不为空集, 利用反射创建 clazz 对应的对象
			if(values.size() > 0){
				entity = clazz.newInstance();
				
				//5. 遍历 Map 对象, 利用反射为 Class 对象的对应的属性赋值. 
				for(Map.Entry<String, Object> entry: values.entrySet()){
					String fieldName = entry.getKey();
					Object value = entry.getValue();
					ReflectionUtils.setFieldValue(entity, fieldName, value);
				}
			}
			

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.release(resultSet, preparedStatement, connection);
		}

		return entity;
	}

}
